#include <fstream>
#include "Poco/Zip/Compress.h"
#include "Poco/Zip/Decompress.h"
#include "Poco/Delegate.h"
#include "Poco/File.h"
#include "Poco/FileStream.h"
#include "Poco/Path.h"
#include "Poco/Zip/ZipArchive.h"
#include "Poco/Zip/ZipStream.h"
#include "Poco/Zip/ZipManipulator.h"
#include "Poco/Zip/SkipCallback.h"
#include "Poco/StreamCopier.h"
#include "Poco/Exception.h"
#include "String/String.h"
#include "Zip.h"

using namespace SystemTool;

class ZipCallBack
{
public:
    void onDone(const void *, const Poco::Zip::ZipLocalFileHeader &hdr)
    {
        std::cout << "Done:" << hdr.getFileName() << std::endl;
    }

    void onDecompressError(const void *pSender, std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string> &info)
    {
        std::cout << "Decompress err:" << info.first.getFileName() << std::endl;
    }
};

// Compress file
bool Zip::Compress(std::string strFileOrDirPath, std::string &strZipFilePath, bool bIsBigFile)
{
    if (strFileOrDirPath.empty())
    {
        return false;
    }

    try
    {
        Poco::File aFile(strFileOrDirPath);
        if (!aFile.exists())
        {
            return false;
        }
        Poco::Path anEntry(strFileOrDirPath);

        std::ofstream os(strZipFilePath, std::ios::binary);
        Poco::Zip::Compress c(os, true, bIsBigFile);

        ZipCallBack ZipCB;
        c.EDone += Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
        if (aFile.isDirectory())
        {
            anEntry.makeDirectory();
            c.addRecursive(anEntry, Poco::Zip::ZipCommon::CL_MAXIMUM, true);
        }
        else
        {
            anEntry.makeFile();
            c.addFile(anEntry, anEntry);
        }
        c.close();
        c.EDone -= Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Compress stream
bool Zip::Compress(std::string strFileOrDirPath, std::ostream &os, bool bIsBigFile)
{
    if (strFileOrDirPath.empty())
    {
        return false;
    }

    try
    {
        Poco::File aFile(strFileOrDirPath);
        if (!aFile.exists())
        {
            return false;
        }
        Poco::Path anEntry(strFileOrDirPath);

        Poco::Zip::Compress c(os, true, bIsBigFile);

        ZipCallBack ZipCB;
        c.EDone += Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
        if (aFile.isDirectory())
        {
            anEntry.makeDirectory();
            c.addRecursive(anEntry, Poco::Zip::ZipCommon::CL_MAXIMUM, true);
        }
        else
        {
            anEntry.makeFile();
            c.addFile(anEntry, anEntry);
        }
        c.close();
        c.EDone -= Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Compress several files or directory
bool Zip::Compress(std::vector<std::string> &vFileTable, std::string &strZipFilePath, bool bIsBigFile)
{
    if (vFileTable.empty())
    {
        return false;
    }

    try
    {
        std::ofstream out(strZipFilePath, std::ios::binary);
        Poco::Zip::Compress c(out, true, bIsBigFile);
        ZipCallBack ZipCB;
        c.EDone += Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
        for (size_t index = 0; index < vFileTable.size(); index++)
        {
            std::string strFilePath = vFileTable[index];
            Poco::File aFile(strFilePath);
            if (!aFile.exists())
            {
                std::cout << "File is not existed:" << strFilePath << std::endl;
            }
            else
            {
                Poco::Path anEntry(strFilePath);
                if (aFile.isDirectory())
                {
                    anEntry.makeDirectory();
                    c.addRecursive(anEntry, Poco::Zip::ZipCommon::CL_MAXIMUM, true);
                }
                else
                {
                    anEntry.makeFile();
                    c.addFile(anEntry, anEntry);
                }
            }
        }
        c.close();
        c.EDone -= Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Compress several files or directory
bool Zip::Compress(std::vector<std::string> &vFileTable, std::ostream &os, bool bIsBigFile)
{
    if (vFileTable.empty())
    {
        return false;
    }

    try
    {
        Poco::Zip::Compress c(os, true, bIsBigFile);
        ZipCallBack ZipCB;
        c.EDone += Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
        for (size_t index = 0; index < vFileTable.size(); index++)
        {
            std::string strFilePath = vFileTable[index];
            Poco::File aFile(strFilePath);
            if (!aFile.exists())
            {
                std::cout << "File is not existed:" << strFilePath << std::endl;
            }
            else
            {
                Poco::Path anEntry(strFilePath);
                if (aFile.isDirectory())
                {
                    anEntry.makeDirectory();
                    c.addRecursive(anEntry, Poco::Zip::ZipCommon::CL_MAXIMUM, true);
                }
                else
                {
                    anEntry.makeFile();
                    c.addFile(anEntry, anEntry);
                }
            }
        }
        c.close();
        c.EDone -= Poco::Delegate<ZipCallBack, const Poco::Zip::ZipLocalFileHeader>(&ZipCB, &ZipCallBack::onDone);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Decompress file/directory
bool Zip::DeCompress(std::string strZipFilePath, std::string &strTargetDirPath)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        // Confirm that taget directory is exist
        String strFinalTargetDirPath = strTargetDirPath;
        strFinalTargetDirPath.Replace("\\", "/");
        Poco::File TagetDir(strFinalTargetDirPath.Data());
        if (!TagetDir.exists())
        {
            TagetDir.createDirectories();
        }

        Poco::FileInputStream ins(strZipFilePath);
        Poco::Zip::Decompress dec(ins, strTargetDirPath);

        ZipCallBack ZipCB;
        dec.EError += Poco::Delegate<ZipCallBack, std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string>>(&ZipCB, &ZipCallBack::onDecompressError);
        dec.decompressAllFiles();
        dec.EError -= Poco::Delegate<ZipCallBack, std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string>>(&ZipCB, &ZipCallBack::onDecompressError);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Decompress stream
bool Zip::DeCompress(std::istream &is, std::string &strTargetDirPath)
{
    if (!is.good())
    {
        return false;
    }

    try
    {
        // Confirm that taget directory is exist
        String strFinalTargetDirPath = strTargetDirPath;
        strFinalTargetDirPath.Replace("\\", "/");
        Poco::File TagetDir(strFinalTargetDirPath.Data());
        if (!TagetDir.exists())
        {
            TagetDir.createDirectories();
        }

        Poco::Zip::Decompress dec(is, strTargetDirPath);

        ZipCallBack ZipCB;
        dec.EError += Poco::Delegate<ZipCallBack, std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string>>(&ZipCB, &ZipCallBack::onDecompressError);
        dec.decompressAllFiles();
        dec.EError -= Poco::Delegate<ZipCallBack, std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string>>(&ZipCB, &ZipCallBack::onDecompressError);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Decompress single file in zip only
bool Zip::DecompressSingleFile(std::string &strZipFilePath, std::string &strFileInZip, std::string &strTargetDirPath)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        // Confirm that taget directory is exist
        String strFinalTargetDirPath = strTargetDirPath;
        strFinalTargetDirPath.Replace("\\", "/");
        Poco::File TagetDir(strFinalTargetDirPath.Data());
        if (!TagetDir.exists())
        {
            TagetDir.createDirectories();
        }

        Poco::FileInputStream ins(strZipFilePath);
        Poco::Zip::ZipArchive arch(ins);
        Poco::Zip::ZipArchive::FileHeaders::const_iterator Iter = arch.findHeader(strFileInZip);
        if (Iter == arch.headerEnd())
        {
            return false;
        }
        Poco::Zip::ZipInputStream zipin(ins, Iter->second);
        std::ofstream os(strTargetDirPath, std::ios::binary);
        Poco::StreamCopier::copyStream(zipin, os);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Find file in zip
bool Zip::FindFileInZip(std::string strZipFilePath, std::string &strFilePathInZip)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        Poco::Zip::ZipManipulator zm(strZipFilePath, false);
        Poco::Zip::ZipArchive arch = zm.commit();
        auto Iter = arch.findHeader(strFilePathInZip);
        if (Iter == arch.headerEnd())
        {
            return false;
        }
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Rename file in zip
bool Zip::RenameFileInZip(std::string strZipFilePath, std::string &strFilePathInZip, std::string &strNewFilePathInZip)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        Poco::Zip::ZipManipulator zm(strZipFilePath, false);
        zm.renameFile(strFilePathInZip, strNewFilePathInZip);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Add new file to zip
bool Zip::AddFileToZip(std::string strZipFilePath, std::string &strFilePathInZip, std::string &strLocalFilePath)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        Poco::Zip::ZipManipulator zm(strZipFilePath, false);
        zm.addFile(strFilePathInZip, strLocalFilePath);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Delete file in zip
bool Zip::DeleteFileInZip(std::string strZipFilePath, std::string &strFilePathInZip)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        Poco::Zip::ZipManipulator zm(strZipFilePath, false);
        zm.deleteFile(strFilePathInZip);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Replace file in zip
bool Zip::ReplaceFileContentInZip(std::string strZipFilePath, std::string &strFilePathInZip, std::string &strLocalFilePath)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        Poco::Zip::ZipManipulator zm(strZipFilePath, false);
        zm.replaceFile(strFilePathInZip, strLocalFilePath);
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}

// Get zip file's info
bool Zip::GetZipInfo(std::string strZipFilePath,
                     int &iZipMajorVersion,
                     int &iZipMinorVersion,
                     int &iCompressMethod,
                     int &iCompressLevel,
                     int &iCrc,
                     bool &bIsEncrypted,
                     int &iLastModifyTime_Ms,
                     int &iCompressedSize,
                     int &iUnCompressedSize,
                     std::string &strFileName)
{
    if (strZipFilePath.empty())
    {
        return false;
    }

    try
    {
        Poco::FileInputStream ins(strZipFilePath);
        Poco::Zip::SkipCallback skip;
        Poco::Zip::ZipLocalFileHeader hdr(ins, false, skip);

        iZipMajorVersion = hdr.getMajorVersionNumber();
        iZipMinorVersion = hdr.getMinorVersionNumber();
        iCompressMethod = hdr.getCompressionMethod();
        iCompressLevel = hdr.getCompressionLevel();
        iCrc = hdr.getCRC();
        bIsEncrypted = hdr.isEncrypted();
        iLastModifyTime_Ms = hdr.lastModifiedAt().timestamp().epochTime() * 1000;
        iCompressedSize = hdr.getCompressedSize();
        iUnCompressedSize = hdr.getUncompressedSize();
        strFileName = hdr.getFileName();
    }
    catch (Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }

    return true;
}
